package com.tiancheng.trade.payment.channel.ums.utils;

import com.tiancheng.trade.payment.channel.ums.UmsProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.PrivateKey;
import java.util.*;

/**
 * @Author: likailun
 * @Description:
 * @Date: create in 2021/8/28 11:04 上午
 */
@Slf4j
public class UmsQrcUtil {

    /**
     * 请求报文签名(使用配置文件中配置的私钥证书)<br>
     * 功能：对请求报文进行签名,并计算赋值certid,signature字段并返回<br>
     * @param reqData 请求报文map<br>
     * @param umsQrcProperties qr配置信息
     * @return　签名后的map对象<br>
     */
    public static Map<String, String> sign(Map<String, String> reqData, UmsProperties umsQrcProperties) {
        reqData = filterBlank(reqData);
        //设置算法类型（acp_sdk.properties配置文件中配置项acpsdk.signMethod)
        reqData.put("signType", umsQrcProperties.getQrcProperties().getSignMethod());

        String version = reqData.get("version");
        if (StringUtils.isEmpty(version)) {
            log.error("version must Not null");
            return reqData;
        }
        signSHA1withRSA(reqData,"UTF-8",null,null);
        return reqData;
    }

    /**
     * 验证签名(SHA-1或SHA-256摘要算法)<br>
     * @param rspData 返回报文数据<br>
     * @param encoding 上送请求报文域encoding字段的值<br>
     * @return true 通过 false 未通过<br>
     */
    public static boolean validate(Map<String, String> rspData, String encoding) {
        if (StringUtils.isEmpty(encoding)) {
            encoding = "UTF-8";
        }
        String signType = rspData.get(UmsConstant.PARAM_SIGNTYPE);
        String version = rspData.get(UmsConstant.PARAM_VERSION);
        if (UmsConstant.VERSION_1_0_0.equals(version)) {
            if(StringUtils.isEmpty(signType) || UmsConstant.SIGNMETHOD_RSA.equals(signType)){
                //SHA1withRSA 不送signMethod默认的签名算法
                return validateSHA1WithRSA(rspData,encoding);
            }
        }
        return false;
    }


    /**
     * sha1withRsa验签方法
     * @param resData
     * @param encoding
     * @return
     */
    private static boolean validateSHA1WithRSA(Map<String,String> resData,String encoding){
        String stringSign = resData.get(UmsConstant.PARAM_SIGNATURE);
        // 从返回报文中获取certId ，然后去证书静态Map中查询对应验签证书对象
        String certId = resData.get(UmsConstant.PARAM_CERTID);
        // 将Map信息转换成key1=value1&key2=value2的形式
        String stringData = coverMap2String(resData);

        try {
            // 验证签名需要用银联发给商户的公钥证书.
            boolean result = SecureUtil.validateSignBySoft(CertUtil
                            .getValidatePublicKey(certId), SecureUtil
                            .base64Decode(stringSign.getBytes(encoding)),
                    SecureUtil.sha1X16(stringData, encoding));
            return result;
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return false;
    }


    /**
     * sha1withRsa算法签名
     * @param data
     * @param encoding
     * @param certPath
     * @param certPwd
     */
    private static void signSHA1withRSA(Map<String, String> data,String encoding,
                                        String certPath,String certPwd){
        String certId ;
        PrivateKey pk ;
        if(!StringUtils.isEmpty(certPath)){
            certId = CertUtil.getCertIdByKeyStoreMap(certPath, certPwd);
            pk = CertUtil.getSignCertPrivateKeyByStoreMap(certPath, certPwd);
        }
        else{
            certId = CertUtil.getSignCertId();
            pk = CertUtil.getSignCertPrivateKey();
        }
        // 设置签名证书序列号
        data.put(UmsConstant.PARAM_CERTID, certId);
        // 将Map信息转换成key1=value1&key2=value2的形式
        String stringData = coverMap2String(data);
        //log.info("打印排序后待签名请求报文串（交易返回11验证签名失败时可以用来同正确的进行比对）:[" + stringData + "]");
        byte[] byteSign = null;
        String stringSign = null;
        try {
            // 通过SHA1进行摘要并转16进制
            byte[] signDigest = SecureUtil.sha1X16(stringData, encoding);
            //log.info("打印摘要（交易返回11验证签名失败可以用来同正确的进行比对）:[" + new String(signDigest)+ "]");
            byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft(pk, signDigest));
            stringSign = new String(byteSign);
            // 设置签名域值
            data.put(UmsConstant.PARAM_SIGNATURE, stringSign);
        } catch (Exception e) {
            log.error("Sign Error", e);
        }
    }


    /**
     * 将Map中的数据转换成key1=value1&key2=value2的形式 不包含签名域signature
     *
     * @param data
     *            待拼接的Map数据
     * @return 拼接好后的字符串
     */
    public static String coverMap2String(Map<String, String> data) {
        TreeMap<String, String> tree = new TreeMap<String, String>();
        Iterator<Map.Entry<String, String>> it = data.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            if (UmsConstant.PARAM_SIGNATURE.equals(en.getKey().trim())) {
                continue;
            }
            tree.put(en.getKey(), en.getValue());
        }
        it = tree.entrySet().iterator();
        StringBuffer sf = new StringBuffer();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            sf.append(en.getKey() + UmsConstant.EQUAL + en.getValue()
                    + UmsConstant.AMPERSAND);
        }
        return sf.substring(0, sf.length() - 1);
    }

    /**
     * 过滤请求报文中的空字符串或者空字符串
     * @param contentData
     * @return
     */
    public static Map<String, String> filterBlank(Map<String, String> contentData){
        Map<String, String> submitFromData = new HashMap<String, String>();
        Set<String> keyset = contentData.keySet();

        for(String key:keyset){
            String value = contentData.get(key);
            if (value != null && !"".equals(value.trim())) {
                // 对value值进行去除前后空处理
                submitFromData.put(key, value.trim());
                //log.info((key + "-->" + String.valueOf(value)));
            }
        }
        return submitFromData;
    }


    /**
     * 对字符串做base64<br>
     * @param rawStr<br>
     * @param encoding<br>
     * @return<br>
     * @throws IOException
     */
    public static String base64Encode(String rawStr,String encoding) throws IOException{
        byte [] rawByte = rawStr.getBytes(encoding);
        return new String(SecureUtil.base64Encode(rawByte),encoding);
    }
    /**
     * 对base64的字符串解base64<br>
     * @param base64Str<br>
     * @param encoding<br>
     * @return<br>
     * @throws IOException
     */
    public static String base64Decode(String base64Str,String encoding) throws IOException{
        byte [] rawByte = base64Str.getBytes(encoding);
        return new String(SecureUtil.base64Decode(rawByte),encoding);
    }
    /**
     * 组装付款方信息
     * @param encoding 编码方式
     * @return 用{}连接并base64后的付款方信息
     */
    public static String getPayerInfo(Map<String, String> payarInfoMap, String encoding) {
        return formInfoBase64(payarInfoMap,encoding);
    }
    /**
     * 组装付款方信息(接入机构配置了敏感信息加密)
     * @param encoding 编码方式
     * @return 用{}连接并base64后的付款方信息
     */
    public static String getPayerInfoWithEncrpyt(Map<String, String> payarInfoMap, String encoding) {
        return formInfoBase64WithEncrpyt(payarInfoMap,encoding);
    }
    /**
     * 用{}连接并base64(接入机构配置了敏感信息加密)
     * @param map
     * @param encoding
     * @return
     */
    public static String formInfoBase64WithEncrpyt(Map<String, String> map,String encoding){
        StringBuffer sf = new StringBuffer();
        String info = sf.append(UmsConstant.LEFT_BRACE).append(coverMap2String(map)).append(UmsConstant.RIGHT_BRACE).toString();
        info = encryptData(info, encoding);
        return info;
    }

    /**
     * 敏感信息加密并做base64(卡号，手机号，cvn2,有效期）<br>
     * @param data 送 phoneNo,cvn2,有效期<br>
     * @param encoding<br>
     * @return 加密的密文<br>
     */
    public static String encryptData(String data, String encoding) {
        return SecureUtil.encryptData(data, encoding, CertUtil
                .getEncryptCertPublicKey());
    }

    /**
     * 组装附加处理条件
     * @param encoding 编码方式
     * @return 用{}连接并base64后的附加处理条件
     */
    public static String getAddnCond(Map<String, String> addnCondMap,String encoding) {
        return formInfoBase64(addnCondMap,encoding);
    }

    /**
     * 用{}连接并base64
     * @param map
     * @param encoding
     * @return
     */
    public static String formInfoBase64(Map<String, String> map,String encoding){
        StringBuffer sf = new StringBuffer();
        String info = sf.append(UmsConstant.LEFT_BRACE).append(coverMap2String(map)).append(UmsConstant.RIGHT_BRACE).toString();
        try {
            info = new String(base64Encode(info, encoding));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return info;
    }

    /**
     * 将形如key=value&key=value的字符串转换为相应的Map对象
     *
     * @param result
     * @return
     */
    public static Map<String, String> convertResultStringToMap(String result) {
        Map<String, String> map = null;

        if (result != null && !"".equals(result.trim())) {
            if (result.startsWith("{") && result.endsWith("}")) {
                result = result.substring(1, result.length() - 1);
            }
            map = parseQString(result);
        }

        return map;
    }

    /**
     * 获取请求参数中所有的信息
     * 当商户上送frontUrl或backUrl地址中带有参数信息的时候，
     * 这种方式会将url地址中的参数读到map中，会导多出来这些信息从而致验签失败，这个时候可以自行修改过滤掉url中的参数或者使用getAllRequestParamStream方法。
     * @param request
     * @return
     */
    public static Map<String, String> getAllRequestParam(
            final HttpServletRequest request) {
        Map<String, String> res = new HashMap<String, String>();
        Enumeration<?> temp = request.getParameterNames();
        if (null != temp) {
            while (temp.hasMoreElements()) {
                String en = (String) temp.nextElement();
                String value = request.getParameter(en);
                res.put(en, value);
                // 在报文上送时，如果字段的值为空，则不上送<下面的处理为在获取所有参数数据时，判断若值为空，则删除这个字段>
                if (res.get(en) == null || "".equals(res.get(en))) {
                    // System.out.println("======为空的字段名===="+en);
                    res.remove(en);
                }
            }
        }
        return res;
    }

    /**
     * 获取敏感信息加密证书的物理序列号<br>
     * @return
     */
    public static String getEncryptCertId(){
        return CertUtil.getEncryptCertId();
    }

    /**
     * 解析应答字符串，生成应答要素
     *
     * @param str
     *            需要解析的字符串
     * @return 解析的结果map
     * @throws UnsupportedEncodingException
     */
    public static Map<String, String> parseQString(String str) {

        Map<String, String> map = new HashMap<String, String>();
        int len = str.length();
        StringBuilder temp = new StringBuilder();
        char curChar;
        String key = null;
        boolean isKey = true;
        boolean isOpen = false;//值里有嵌套
        char openName = 0;
        if(len>0){
            for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
                curChar = str.charAt(i);// 取当前字符
                if (isKey) {// 如果当前生成的是key

                    if (curChar == '=') {// 如果读取到=分隔符
                        key = temp.toString();
                        temp.setLength(0);
                        isKey = false;
                    } else {
                        temp.append(curChar);
                    }
                } else  {// 如果当前生成的是value
                    if(isOpen){
                        if(curChar == openName){
                            isOpen = false;
                        }

                    }else{//如果没开启嵌套
                        if(curChar == '{'){//如果碰到，就开启嵌套
                            isOpen = true;
                            openName ='}';
                        }
                        if(curChar == '['){
                            isOpen = true;
                            openName =']';
                        }
                    }

                    if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域，这时将map里添加
                        putKeyValueToMap(temp, isKey, key, map);
                        temp.setLength(0);
                        isKey = true;
                    } else {
                        temp.append(curChar);
                    }
                }

            }
            putKeyValueToMap(temp, isKey, key, map);
        }
        return map;
    }

    private static void putKeyValueToMap(StringBuilder temp, boolean isKey,
                                         String key, Map<String, String> map) {
        if (isKey) {
            key = temp.toString();
            if (key.length() == 0) {
                throw new RuntimeException("QString format illegal");
            }
            map.put(key, "");
        } else {
            if (key.length() == 0) {
                throw new RuntimeException("QString format illegal");
            }
            map.put(key, temp.toString());
        }
    }
    /**
     * 将Map存储的对象，转换为key=value&key=value的字符(通知返回报文不做url编码)
     *
     * @param respParam
     * @return
     */
    public static String getRequestParamString(Map<String, String> respParam) {

        StringBuffer sf = new StringBuffer("");
        String rspstr = "";
        if (null != respParam && 0 != respParam.size()) {
            for (Map.Entry<String, String> en : respParam.entrySet()) {
                sf.append(en.getKey()
                        + "="
                        + (null == en.getValue() || "".equals(en.getValue()) ? "" : en.getValue()) + "&");
            }
            rspstr = sf.substring(0, sf.length() - 1);
        }
        //log.info("Resp Message:{}",rspstr);
        return rspstr;
    }



}
